@inject ILogger<ImportServiceCard> Logger
@inject CredentialService CredentialService
@inject DbService DbService
@inject NavigationManager NavigationManager
@inject GlobalNotificationService GlobalNotificationService
@inject HttpClient HttpClient
@inject IStringLocalizerFactory LocalizerFactory
@using Microsoft.Extensions.Localization
@using AliasVault.ImportExport.Importers
@using AliasVault.ImportExport.Models
@using AliasVault.Shared.Models.WebApi.Favicon

<div @onclick="OpenImportModal" class="flex flex-col p-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
    <div class="flex items-center">
        <div class="w-12 h-12 mr-3 flex-shrink-0">
            @if (!string.IsNullOrEmpty(LogoUrl))
            {
                <img src="@LogoUrl" alt="@ServiceName logo" class="w-full h-full object-contain" />
            }
            else
            {
                <div class="w-full h-full bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center">
                    <span class="text-gray-500 dark:text-gray-400 text-xs">@Localizer["NoLogoText"]</span>
                </div>
            }
        </div>
        <div>
            <h4 class="text-lg font-semibold dark:text-white">@ServiceName</h4>
            @if (!string.IsNullOrEmpty(Description))
            {
                <p class="text-sm text-gray-500 dark:text-gray-400">@Description</p>
            }
        </div>
    </div>
</div>

@if (IsModalOpen)
{
    <ClickOutsideHandler OnClose="CloseModal" ContentId="importServiceModal">
        <ModalWrapper OnEnter="HandleModalConfirm">
            <div id="importServiceModal" class="relative top-20 mx-auto p-5 shadow-lg rounded-md bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-400 md:min-w-[32rem]">
                <div class="bg-white dark:bg-gray-800 rounded-lg p-4 w-full mx-auto">
                    <div class="flex justify-between items-center mb-4">
                        <div class="flex"><img src="@LogoUrl" alt="@ServiceName logo" class="w-8 h-8 float-left mr-4" /><h3 class="text-xl font-semibold dark:text-white">@string.Format(Localizer["ImportFromServiceTitle"], ServiceName)</h3></div>
                        <button @onclick="CloseModal" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300">
                            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                            </svg>
                        </button>
                    </div>

                    <div class="w-full bg-gray-200 rounded-full h-2.5 mb-4 dark:bg-gray-700">
                        <div class="bg-primary-600 h-2.5 rounded-full transition-all duration-300" style="width: @(GetProgressPercentage())%"></div>
                    </div>
                    @switch (CurrentStep)
                    {
                        case ImportStep.FileUpload:
                            <div class="max-w-lg mx-auto">
                                @if (!string.IsNullOrEmpty(ImportError))
                                {
                                    <div class="mb-4 p-4 text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
                                        @ImportError
                                    </div>
                                }

                                @if (IsImporting)
                                {
                                    <LoadingIndicator />
                                }

                                <div class="@(IsImporting ? "hidden" : "")">
                                    @ChildContent
                                    <div class="mb-4 bg-amber-50 border border-amber-400 dark:bg-amber-800/30 dark:border-amber-500/50 rounded-lg p-4">
                                        <p class="mb-4 text-gray-700 dark:text-gray-200">@string.Format(Localizer["UploadExportFileText"], ServiceName)</p>
                                        <InputFile OnChange="HandleFileUpload" class="text-gray-700 dark:text-gray-200 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 dark:file:bg-primary-900/40 dark:file:text-primary-300 dark:hover:file:bg-primary-800/60" />
                                    </div>
                                    <div class="flex justify-end mt-6 space-x-2">
                                        <Button OnClick="@CloseModal" Color="secondary">@Localizer["CancelButton"]</Button>
                                    </div>
                                </div>
                            </div>
                            break;

                        case ImportStep.Preview:
                            <div class="mb-4">
                                @if (DuplicateCredentialsCount > 0)
                                {
                                    <div class="p-4 mb-4 text-blue-700 bg-blue-100 rounded-lg dark:bg-blue-800/30 dark:text-blue-300" role="alert">
                                        <p>@DuplicateCredentialsCount duplicate credential(s) were found and will not be imported.</p>
                                    </div>
                                }

                                @if (ImportedCredentials.Count == 0)
                                {
                                    <div class="p-4 mb-4 text-amber-700 bg-amber-100 rounded-lg dark:bg-amber-800/30 dark:text-amber-300" role="alert">
                                        <p>No new credentials were found to import.</p>
                                    </div>
                                }
                                else
                                {
                                    <p class="mb-4 text-gray-700 dark:text-gray-300">Check if the following detected credentials look correct before continuing:</p>
                                    <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                                        <thead class="bg-gray-50 dark:bg-gray-700">
                                            <tr>
                                                <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Service</th>
                                                <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Username</th>
                                                <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Password</th>
                                            </tr>
                                        </thead>
                                        <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
                                            @foreach (var credential in ImportedCredentials.Take(3))
                                            {
                                                <tr>
                                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">@credential.ServiceName</td>
                                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">@credential.Username</td>
                                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">@(new string('*', credential.Password?.Length ?? 0))</td>
                                                </tr>
                                            }
                                        </tbody>
                                    </table>
                                    @if (ImportedCredentials.Count > 3)
                                    {
                                        <p class="mt-2 text-sm text-gray-500 dark:text-gray-400">... and @(ImportedCredentials.Count - 3) more credentials</p>
                                    }
                                }
                            </div>
                            @if (ImportedCredentials.Count > 0)
                            {
                                <div class="mb-4">
                                    <label class="inline-flex items-center">
                                        <input type="checkbox" @bind="ExtractFavicons" class="form-checkbox h-4 w-4 text-primary-600 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700">
                                        <span class="ml-2 text-gray-700 dark:text-gray-300">Extract favicons for services with URLs</span>
                                    </label>
                                </div>
                            }
                            <div class="flex justify-end mt-6 space-x-2">
                                <Button OnClick="@HandlePreviousStep" Color="secondary">Back</Button>
                                @if (ImportedCredentials.Count > 0)
                                {
                                    <Button OnClick="@HandleNextStep" Color="primary">@Localizer["NextButton"]</Button>
                                }
                            </div>
                            break;

                        case ImportStep.Confirm:
                            <div class="max-w-lg mx-auto">
                                @if (IsImporting)
                                {
                                    @if (IsExtractingFavicons)
                                    {
                                        <div class="text-center">
                                            <LoadingIndicator />
                                            <p class="mt-4 text-gray-700 dark:text-gray-300">Extracting favicons... @(FaviconExtractionProgress)/@(TotalFaviconsToExtract)</p>
                                            <div class="w-full bg-gray-200 rounded-full h-2.5 mt-4 dark:bg-gray-700">
                                                <div class="bg-primary-600 h-2.5 rounded-full transition-all duration-300" style="width: @(GetFaviconProgressPercentage())%"></div>
                                            </div>
                                            <div class="mt-4">
                                                <Button OnClick="@CancelFaviconExtraction" Color="secondary">Cancel</Button>
                                            </div>
                                        </div>
                                    }
                                    else
                                    {
                                        <LoadingIndicator />
                                    }
                                }
                                else {
                                    <div class="mb-4">
                                        <p class="mb-4 text-gray-700 dark:text-gray-300">Are you sure you want to import (@ImportedCredentials.Count) credentials? Note: the import process can take a short while.</p>
                                        @if (ExtractFavicons)
                                        {
                                            <div class="p-4 mb-4 text-amber-700 bg-amber-100 rounded-lg dark:bg-amber-800/30 dark:text-amber-300" role="alert">
                                                <p>Note: Favicon extraction is enabled. This process can take several minutes depending on the number of credentials with URLs. Please keep the page open.</p>
                                            </div>
                                        }
                                    </div>
                                    <div class="flex justify-end mt-6 space-x-2">
                                        <Button OnClick="@HandlePreviousStep" Color="secondary">@Localizer["BackButton"]</Button>
                                        <Button OnClick="@HandleModalConfirm" Color="primary">@Localizer["ImportButton"]</Button>
                                    </div>
                                }
                            </div>
                            break;
                    }
                </div>
            </div>
        </ModalWrapper>
    </ClickOutsideHandler>
}

@code {
    private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.ImportExport.ImportServiceCard", "AliasVault.Client");

    private enum ImportStep
    {
        FileUpload,
        Preview,
        Confirm
    }

    /// <summary>
    /// The name of the service.
    /// </summary>
    [Parameter]
    public string ServiceName { get; set; } = string.Empty;

    /// <summary>
    /// The description of the service.
    /// </summary>
    [Parameter]
    public string Description { get; set; } = string.Empty;

    /// <summary>
    /// The URL of the logo of the service.
    /// </summary>
    [Parameter]
    public string LogoUrl { get; set; } = string.Empty;

    /// <summary>
    /// The event callback for when the import is confirmed.
    /// </summary>
    [Parameter]
    public EventCallback OnImportConfirmed { get; set; }

    /// <summary>
    /// The callback for processing the file.
    /// </summary>
    [Parameter]
    public Func<string, Task<List<ImportedCredential>>> ProcessFileCallback { get; set; } = null!;

    private bool IsModalOpen { get; set; } = false;
    private bool IsImporting { get; set; } = false;
    private string? ImportError { get; set; }
    private string? ImportSuccessMessage { get; set; }
    private ImportStep CurrentStep { get; set; } = ImportStep.FileUpload;

    /// <summary>
    /// Child content which is shown in the modal popup. This can contain custom instructions.
    /// </summary>
    [Parameter]
    public RenderFragment ChildContent { get; set; } = null!;

    /// <summary>
    /// The imported credentials.
    /// </summary>
    private List<ImportedCredential> ImportedCredentials { get; set; } = new();

    private bool ExtractFavicons { get; set; } = true;
    private bool IsExtractingFavicons { get; set; }
    private int FaviconExtractionProgress { get; set; }
    private int TotalFaviconsToExtract { get; set; }
    private CancellationTokenSource? FaviconExtractionCancellation { get; set; }
    private Dictionary<string, byte[]> ExtractedFavicons { get; set; } = new();

    private int DuplicateCredentialsCount { get; set; } = 0;

    /// <summary>
    /// Sets the imported credentials and continues to the preview step.
    /// </summary>
    /// <param name="importedCredentials">The imported credentials.</param>
    public async Task SetImportedCredentials(List<ImportedCredential> importedCredentials)
    {
        ImportedCredentials = importedCredentials;

        // Continue to step 2.
        await HandleNextStep();
    }

    /// <summary>
    /// Called when a file is selected in the parent file upload step.
    /// </summary>
    public async Task FileSelected()
    {
        // If the file is selected, we can go to the preview step.
        await HandleNextStep();
    }

    /// <summary>
    /// Opens the import modal.
    /// </summary>
    protected virtual void OpenImportModal()
    {
        IsModalOpen = true;
        CurrentStep = ImportStep.FileUpload;
        StateHasChanged();
    }

    /// <summary>
    /// Closes the import modal.
    /// </summary>
    protected virtual void CloseModal()
    {
        IsModalOpen = false;
        CurrentStep = ImportStep.FileUpload;
        ImportError = null;
        ImportSuccessMessage = null;
        ImportedCredentials.Clear();
        StateHasChanged();
    }

    /// <summary>
    /// Handles the next step in the import process.
    /// </summary>
    protected virtual async Task HandleNextStep()
    {
        if (CurrentStep == ImportStep.Preview)
        {
            CurrentStep = ImportStep.Confirm;
        }
        else if (CurrentStep == ImportStep.Confirm)
        {
            await HandleModalConfirm();
        }
    }

    /// <summary>
    /// Handles the previous step in the import process.
    /// </summary>
    protected virtual void HandlePreviousStep()
    {
        if (CurrentStep == ImportStep.Preview)
        {
            CurrentStep = ImportStep.FileUpload;
        }
        else if (CurrentStep == ImportStep.Confirm)
        {
            CurrentStep = ImportStep.Preview;
        }
    }

    /// <summary>
    /// Handles the modal confirm.
    /// </summary>
    protected virtual async Task HandleModalConfirm()
    {
        if (IsImporting)
        {
            return;
        }

        await InitializeImport();

        try
        {
            if (ExtractFavicons)
            {
                await ExtractFaviconsForCredentials();
                if (FaviconExtractionCancellation?.Token.IsCancellationRequested == true)
                {
                    CleanupImport();
                    return;
                }
            }

            await ImportCredentialsToDatabase();
        }
        catch (Exception ex)
        {
            ImportError = $"Error importing credentials: {ex.Message}";
        }
        finally
        {
            CleanupImport();
        }
    }

    /// <summary>
    /// Initializes the import.
    /// </summary>
    private async Task InitializeImport()
    {
        IsImporting = true;
        ImportError = null;
        ImportSuccessMessage = null;
        StateHasChanged();

        // Let UI update to start showing the loading indicator
        await Task.Delay(50);
    }

    /// <summary>
    /// Cleans up the import.
    /// </summary>
    private void CleanupImport()
    {
        IsImporting = false;
        IsExtractingFavicons = false;
        StateHasChanged();
    }

    /// <summary>
    /// Extracts favicons for credentials.
    /// </summary>
    private async Task ExtractFaviconsForCredentials()
    {
        IsExtractingFavicons = true;
        FaviconExtractionProgress = 0;
        ExtractedFavicons.Clear();
        FaviconExtractionCancellation = new CancellationTokenSource();
        StateHasChanged();

        var credentialsWithUrls = ImportedCredentials.Where(c => !string.IsNullOrEmpty(c.ServiceUrl)).ToList();
        TotalFaviconsToExtract = credentialsWithUrls.Count;

        foreach (var credential in credentialsWithUrls)
        {
            if (FaviconExtractionCancellation.Token.IsCancellationRequested)
            {
                break;
            }

            await ExtractFaviconForCredential(credential);
            FaviconExtractionProgress++;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Extracts a favicon for a credential.
    /// </summary>
    /// <param name="credential">The credential to extract the favicon for.</param>
    private async Task ExtractFaviconForCredential(ImportedCredential credential)
    {
        try
        {
            var apiReturn = await HttpClient.GetFromJsonAsync<FaviconExtractModel>($"v1/Favicon/Extract?url={credential.ServiceUrl!}");
            if (apiReturn?.Image is not null)
            {
                ExtractedFavicons[credential.ServiceUrl!] = apiReturn.Image;
            }
        }
        catch
        {
            // Ignore favicon extraction errors
        }
    }

    /// <summary>
    /// Imports the credentials to the database.
    /// </summary>
    private async Task ImportCredentialsToDatabase()
    {
        var credentials = BaseImporter.ConvertToCredential(ImportedCredentials);
        foreach (var credential in credentials)
        {
            await ProcessSingleCredential(credential);
            await Task.Delay(2); // Small delay to avoid blocking the UI thread
        }

        var success = await DbService.SaveDatabaseAsync();
        if (success)
        {
            GlobalNotificationService.AddSuccessMessage($"Successfully imported {ImportedCredentials.Count} credentials.");
            NavigationManager.NavigateTo("/credentials");
        }
        else
        {
            ImportError = "Error saving database.";
        }
    }

    /// <summary>
    /// Processes a single credential.
    /// </summary>
    /// <param name="credential">The credential to process.</param>
    private async Task ProcessSingleCredential(Credential credential)
    {
        if (!string.IsNullOrEmpty(credential.Service.Url) && ExtractedFavicons.TryGetValue(credential.Service.Url, out var favicon))
        {
            credential.Service.Logo = favicon;
        }
        await CredentialService.InsertEntryAsync(credential, false, false);
    }

    /// <summary>
    /// Handles the file upload.
    /// </summary>
    private async Task HandleFileUpload(InputFileChangeEventArgs e)
    {
        if (string.IsNullOrEmpty(e.File.Name))
        {
            ImportError = $"Please select a valid {ServiceName} export file to import";
            return;
        }

        if (e.File.Name.EndsWith(".zip"))
        {
            ImportError = $"Please unzip the {ServiceName} export file before importing, please read the instructions below for more information.";
            return;
        }

        try
        {
            IsImporting = true;
            StateHasChanged();

            // Limit file size to 10MB
            if (e.File.Size > 10 * 1024 * 1024)
            {
                throw new ArgumentException("File size exceeds 10MB limit");
            }

            await using var stream = e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
            using var reader = new StreamReader(stream);
            var fileContents = await reader.ReadToEndAsync();

            var processingTask = ProcessFileCallback(fileContents);
            var delayTask = Task.Delay(500);

            await Task.WhenAll(processingTask, delayTask);

            ImportedCredentials = await processingTask;

            // Detect and remove duplicates before showing the preview
            await DetectAndRemoveDuplicates();

            CurrentStep = ImportStep.Preview;
        }
        catch (Exception ex)
        {
            ImportError = $"Error processing {ServiceName} export file. Please check the file format and try again.";
            Logger.LogError(ex, "Error processing {ServiceName} export file", ServiceName);
        }
        finally
        {
            IsImporting = false;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Detects and removes duplicates from the import list.
    /// </summary>
    private async Task DetectAndRemoveDuplicates()
    {
        var existingCredentials = await CredentialService.LoadAllAsync();
        var duplicates = ImportedCredentials.Where(imported =>
            existingCredentials.Any(existing =>
                existing.Service.Name != null && existing.Service.Name.Equals(imported.ServiceName, StringComparison.OrdinalIgnoreCase) &&
                existing.Username != null && existing.Username.Equals(imported.Username, StringComparison.OrdinalIgnoreCase) &&
                existing.Passwords.Any(p => p.Value != null && p.Value.Equals(imported.Password, StringComparison.OrdinalIgnoreCase))
            )).ToList();

        DuplicateCredentialsCount = duplicates.Count;

        // Remove duplicates from the import list
        ImportedCredentials = ImportedCredentials.Except(duplicates).ToList();
    }

    /// <summary>
    /// Calculates the progress percentage based on the current step in the import process.
    /// </summary>
    /// <returns>The progress percentage as an integer.</returns>
    private int GetProgressPercentage()
    {
        return (int)CurrentStep * 100 / (Enum.GetValues(typeof(ImportStep)).Length - 1);
    }

    private int GetFaviconProgressPercentage()
    {
        if (TotalFaviconsToExtract == 0) {
            return 0;
        }

        return (FaviconExtractionProgress * 100) / TotalFaviconsToExtract;
    }

    private void CancelFaviconExtraction()
    {
        FaviconExtractionCancellation?.Cancel();
        IsExtractingFavicons = false;
        StateHasChanged();
    }
}
